﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Foundation.Metadata;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using Windows.UI;
using Windows.UI.Core;
using Windows.UI.Xaml.Media.Imaging; 
using EditoneLib;

namespace EditoneLibRT
{
    [Activatable(1)]
    public sealed class ClipVisualizer
    {
        public static IAsyncOperation<IList<string>> GetBase64PngArrayAsync(
            Clip clip, 
            long startOffsetTicks, 
            long lengthTicks, 
            long stepTicks,
            int height
            )
        {
            CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
            var instance = new ClipVisualizer()
            {
                _clip = clip,
                _height = height,
                _start = TimeSpan.FromTicks(startOffsetTicks),
                _end = TimeSpan.FromTicks(startOffsetTicks + lengthTicks),
                _step = TimeSpan.FromTicks(stepTicks),
                _token = cancellationTokenSource.Token
            };


            var task = Task.Run(async () => {
                var strings = await instance.GetPngStringsAsync();
                return (IList<string>)strings;
            }, cancellationTokenSource.Token);
            
            return task.AsAsyncOperation();
        }

        private Clip _clip;
        private int _height;
        private TimeSpan _start;
        private TimeSpan _end;
        private TimeSpan _step;
        private CancellationToken _token;

        private async Task<List<string>> GetPngStringsAsync()
        {
            if (_clip.Type == Clip.LoadingType)
            {
                return null;
            }

            int width;
            List<byte[]> bitmaps = Draw(_clip.DyClip, out width);
            var strings = new List<string>(bitmaps.Count);

            foreach (var bmp in bitmaps)
            {
                _token.ThrowIfCancellationRequested();
                using (var stream = new InMemoryRandomAccessStream())
                {
                    var enc = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);

                    enc.SetPixelData(
                        BitmapPixelFormat.Rgba8,
                        BitmapAlphaMode.Premultiplied,
                        (uint) width, (uint) _height, 96, 96,
                        bmp);
                    await enc.FlushAsync();
                    _token.ThrowIfCancellationRequested();

                    using (var reader = new BinaryReader(stream.AsStreamForRead()))
                    {
                        strings.Add(
                            "data:image/png;base64," + Convert.ToBase64String(reader.ReadBytes((int) reader.BaseStream.Length))
                            );
                    }
                }
            }
            return strings;
        }

        private List<byte[]> Draw<TClip>(TClip audioClip, out int width) where TClip: BaseAudioClip
        {
            _token.ThrowIfCancellationRequested();

            width = (int)Math.Ceiling((_end.Ticks - _start.Ticks)/((double)_step.Ticks));

            var bitmaps = new List<byte[]>(audioClip.NumChannels); //RGBA buffer
            for (int ch = 0; ch < audioClip.NumChannels; ch++)
            {
                bitmaps.Add(new byte[width*_height*4]);
            }
            _token.ThrowIfCancellationRequested();

            int x = 0, y = 0;
            for (var from = _start; from < _end; from+=_step)
            {
                var to = from + _step;

                float[,] buffer;
                lock (audioClip.Stream)
                {
                    buffer = audioClip[from, to];
                }

                var info = GetStepDrawInfo(buffer);
                for (int ch = 0; ch < audioClip.NumChannels; ch++)
                {
                    var hh = _height/2;
                    var y1 = hh - info[ch].Max*hh;
                    var y2 = hh - info[ch].AvgPlus*hh;
                    var y3 = hh - info[ch].AvgMinus*hh;
                    var y4 = hh - info[ch].Min*hh;
                    for (y = 0; y < _height; y++)
                    {
                        byte alpha;
                        if (y < y4 && y >= y1)
                        {
                            alpha = 100;
                            if (y < y3 && y >= y2)
                            {
                                alpha = 255;
                            }
                        }
                        else
                        {
                            alpha = 0;
                        }
                        var i = (x + y*width)*4;

                        bitmaps[ch][i] = 255;
                        bitmaps[ch][i + 1] = 255;
                        bitmaps[ch][i + 2] = 255;
                        bitmaps[ch][i + 3] = alpha;
                    }
                }
                x++;
                _token.ThrowIfCancellationRequested();
            }

            return bitmaps;
        }

        private static StepDrawInfo[] GetStepDrawInfo(float[,] buffer)
        {
            var blockCount = buffer.GetLength(0);
            var infos = new StepDrawInfo[buffer.GetLength(1)];
            for (int ch = 0; ch < buffer.GetLength(1); ch++)
            {
                infos[ch].Max = Single.NegativeInfinity;
                infos[ch].Min = Single.PositiveInfinity;
            }

            for (int i = 0; i < blockCount; i++)
            {
                for (int ch = 0; ch < buffer.GetLength(1); ch++)
                {
                    var val = buffer[i, ch];
                    if (val > infos[ch].Max)
                    {
                        infos[ch].Max = val;
                    }

                    if (val < infos[ch].Min)
                    {
                        infos[ch].Min = val;
                    }

                    if (val < 0)
                    {
                        infos[ch].AvgMinus += val/blockCount;
                    }
                    else
                    {
                        infos[ch].AvgPlus += val/blockCount;
                    }
                }
            }
            return infos;
        }

        private struct StepDrawInfo
        {
            public float Max, Min, AvgPlus, AvgMinus;

        }
    }
}
